Skip to content

Sanc 28 implement driver trip assignment system#56

Open
Lujarios wants to merge 24 commits intomainfrom
SANC-28-implement-driver-trip-assignment-system
Open

Sanc 28 implement driver trip assignment system#56
Lujarios wants to merge 24 commits intomainfrom
SANC-28-implement-driver-trip-assignment-system

Conversation

@Lujarios
Copy link
Contributor

@Lujarios Lujarios commented Feb 7, 2026

Summary by CodeRabbit

  • New Features

    • Submit phone numbers with bookings
    • Driver availability checks with overlap and travel-time validation (maps-based lookup & caching)
    • Estimated end time auto-calculated from travel duration + pickup wait
    • Enhanced booking debug UI: richer date/time inputs, day/driver views, availability table, and booking controls (create/update/cancel)
  • Bug Fixes

    • Improved end-time validation and standardized end-time formatting
  • Chores

    • .gitignore updated to track additional metadata

@vercel
Copy link

vercel bot commented Feb 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
salvationarmy Ready Ready Preview, Comment Feb 21, 2026 6:48pm

@coderabbitai
Copy link

coderabbitai bot commented Feb 7, 2026

📝 Walkthrough

Walkthrough

Removes drizzle/meta from .gitignore; adds driver-assignment constants and datetime rounding; integrates Google Maps Distance Matrix for travel-time lookups; adds driver availability/travel validation and new booking procedures; expands booking debug UI and styles; adds DB indexes for driver overlap checks.

Changes

Cohort / File(s) Summary
Git ignore
/.gitignore
Removes the drizzle/meta ignore entry (no other .gitignore changes).
Booking Debug UI
src/app/debug/bookings/BookingDebugPage.module.scss, src/app/debug/bookings/page.tsx
Adds styles and a feature-rich booking debug page: day/time inputs, driver select, availability alerts, booked-slots table, estimated end-time display, driver schedule rendering, and related UI helpers/state.
Driver assignment constants
src/constants/driver-assignment.ts
Adds numeric constants: PICKUP_WAIT_TIME_MINUTES, TRAVEL_BUFFER_MINUTES, TIME_SLOT_ROUNDING_MINUTES, MAX_GAP_MINUTES_FOR_TRAVEL_CHECK.
Datetime utilities
src/lib/datetime.ts
Adds roundUpToNearestIncrement(date, incrementMinutes) to round Date objects up to configured minute increments.
Google Maps integration
src/lib/google-maps.ts
Adds TravelTimeOptions and getTravelTimeMinutes(origin, destination, options) to call Distance Matrix API with validation, error handling, and null fallback.
Bookings API & validation
src/server/api/routers/bookings.ts
Adds overlap and travel-time validation, in-memory travel-time caching with fallback, authorization helper, new procedures (getCurrentUser, listDrivers, isDriverAvailable, getEstimatedEndTime), includes phoneNumber in create/update, and invokes validation during create/update; updates getAll, getById, update, cancel to use access checks.
DB schema / Indexes
src/server/db/booking-schema.ts
Adds indexes: bookings_driver_overlap_idx (driverId, status, startTime, endTime) and bookings_driver_end_time_idx (driverId, endTime) alongside existing startTime index.

Sequence Diagram

sequenceDiagram
    actor User
    participant Frontend as BookingDebugPage
    participant API as Bookings Router
    participant DB as Database
    participant Maps as GoogleMaps API

    User->>Frontend: Request availability / submit booking
    Frontend->>API: isDriverAvailable(driverId, startTime, endTime)
    API->>DB: findOverlappingBooking(driverId, startTime, endTime)
    DB-->>API: overlapping booking? / adjacent bookings
    alt overlap detected
        API-->>Frontend: {available:false, reason:"overlap"}
        Frontend-->>User: Show overlap alert
    else no overlap
        API->>Maps: getTravelTimeMinutes(prev.pickup, new.pickup)
        Maps-->>API: travel minutes (or null)
        API->>Maps: getTravelTimeMinutes(new.destination, next.pickup)
        Maps-->>API: travel minutes (or null)
        API->>API: validateTravelBetweenAdjacentBookings(...)
        alt travel validation fails
            API-->>Frontend: {available:false, reason}
            Frontend-->>User: Show travel-time alert
        else validation succeeds
            API-->>Frontend: {available:true}
            Frontend-->>User: Enable submission
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • jason-duong4509
  • burtonjong
  • themaxboucher
  • promatty
  • JustinTan-1

Poem

🐰 I hopped through slots from dawn till night,
Rounded times so bookings fit just right,
Asked Maps for minutes, checked each gap,
Guarded drivers' routes, tightened the map,
A rabbit cheers — schedules take flight!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective of the pull request, which is implementing a driver trip assignment system as indicated by the PR objectives and the substantial changes across multiple files related to driver availability, booking validation, and travel time calculations.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch SANC-28-implement-driver-trip-assignment-system

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@src/app/debug/bookings/page.tsx`:
- Around line 322-334: The onChange handler for DateInput uses
v.toISOString().slice(0,10) when v is a Date which converts to UTC and causes
off-by-one days in UTC+ timezones; update the branch handling v as a Date to
build the local date instead (use v.getFullYear(), v.getMonth(), v.getDate() or
construct new Date(v.getFullYear(), v.getMonth(), v.getDate())) and then call
setSelectedDay with that local-date value; keep the string branch parsing logic
for string inputs unchanged and ensure you still null-check v and call
setSelectedDay(null) when v is null (locations: the onChange handler, variable
v, and setSelectedDay).

In `@src/lib/datetime.ts`:
- Around line 3-14: Update the docstring in src/lib/datetime.ts for the rounding
function (the top comment block describing "Rounds a date up to the nearest time
slot increment") to correct the example showing 14:45 → 15:00; change it to
14:45 → 14:45 so the example accurately reflects that inputs already on the
increment boundary are unchanged (e.g., 14:07 → 14:15, 14:15 → 14:15, 14:16 →
14:30, 14:45 → 14:45).

In `@src/lib/google-maps.ts`:
- Around line 34-42: The JSDoc for the Google Maps travel-time function is
inaccurate: only the missing-API-key check actually throws, while HTTP/API
errors are caught and the function returns null; update the docblock for the
function (the "Calculates travel time between two addresses..." comment) to
remove or narrow the `@throws` description to only the missing API key case
(mention the specific missing-key check) and clarify in `@returns` that API
request failures result in a null return rather than an exception; reference the
function name/description in the file so you update the correct comment.

In `@src/server/api/routers/bookings.ts`:
- Around line 514-528: The current guard only calls validateDriverForSlot when
updatesToApply.driverId is present, so changes to startTime/endTime/addresses on
a booking with an existing driver skip re-validation; change the condition to
also run validation when there is an existing.driverId and any of
startTime/endTime/pickupAddress/destinationAddress are being changed. Inside
that block use driverId = updatesToApply.driverId ?? existing.driverId and
compute proposedStart/proposedEnd/proposedPickup/proposedDest the same way, then
await validateDriverForSlot(ctx, { driverId, startTime: proposedStart, endTime:
proposedEnd, pickupAddress: proposedPickup, destinationAddress: proposedDest,
excludeBookingId: id }); ensure the condition references
updatesToApply.startTime, updatesToApply.endTime, updatesToApply.pickupAddress,
and updatesToApply.destinationAddress and existing.driverId.
- Around line 222-234: listDrivers currently returns driver emails to any
authenticated caller; restrict access by checking the caller's role before
selecting the email column. In the listDrivers handler (protectedProcedure ->
listDrivers) either (A) enforce admin-only access by using your admin middleware
or checking ctx.session.user.role === "admin" and aborting with an unauthorized
error if not admin, or (B) keep the route public to authenticated users but
remove the email from the select for non-admins (select only id and name when
ctx.session.user.role !== "admin"). Update the select projection (the current
select of user.id, user.name, user.email) and the access check in listDrivers
accordingly so emails are only returned to admins.
🧹 Nitpick comments (5)
.gitignore (1)

12-13: Redundant ignore pattern.

Since drizzle on line 13 ignores the entire directory, the drizzle/meta entry on line 12 is redundant and can be removed.

♻️ Proposed cleanup
-drizzle/meta
 drizzle
src/app/debug/bookings/page.tsx (3)

83-83: endPickerValue state is set but never read.

endPickerValue is written on lines 184 and 225 but never consumed in the render output. The "Ends" field (line 442) reads from estimatedEndQuery.data?.estimatedEndTime directly. Remove the unused state to avoid confusion.

🧹 Remove unused state
- const [endPickerValue, setEndPickerValue] = useState<string | null>(null);

And remove references on lines 184 and 225:

  useEffect(() => {
    if (estimatedEndQuery.data) {
-     setEndPickerValue(estimatedEndQuery.data.estimatedEndTime);
      form.setFieldValue("end", estimatedEndQuery.data.estimatedEndTime);
    }
  }, [estimatedEndQuery.data, form.setFieldValue]);
      form.reset();
      // ... reset selectedDay / startTimeOfDay ...
-     setEndPickerValue(null);

419-437: Remove commented-out code.

Lines 423–426 contain commented-out JSX for location display. If this is intentionally omitted, remove it to keep the debug output clean.


364-407: Consider extracting the IIFE into a named component or variable.

The inline IIFE (() => { ... })() for rendering the driver bookings table works but reduces readability. Extracting it to a small helper or a useMemo-derived variable would improve clarity.

src/server/api/routers/bookings.ts (1)

277-308: getEstimatedEndTime silently uses a fallback when the API call fails — consider surfacing this.

When getTravelTimeMinutes returns null, the fallback of 15 minutes is used without informing the client. If the actual travel time is significantly longer, the estimated end time could be too short, leading to booking conflicts. Consider returning a flag (e.g., usedFallback: true) so the UI can display a warning.

💡 Example: surface fallback usage
  .query(async ({ input }) => {
-     const drivingTimeMinutes =
-       (await getTravelTimeMinutes(input.pickupAddress, input.destinationAddress)) ??
-       FALLBACK_TRAVEL_MINUTES;
+     const apiResult = await getTravelTimeMinutes(input.pickupAddress, input.destinationAddress);
+     const usedFallback = apiResult === null;
+     const drivingTimeMinutes = apiResult ?? FALLBACK_TRAVEL_MINUTES;

      // ... existing calculation ...

      return {
        location1: input.pickupAddress,
        location2: input.destinationAddress,
        drivingTimeMinutes,
        totalBookingMinutes,
        startTime: input.startTime,
        estimatedEndTime,
+       usedFallback,
      };
    }),

Comment on lines +34 to +42
/**
* Calculates travel time between two addresses using Google Maps Distance Matrix API
*
* @param originAddress - Origin address as a string
* @param destinationAddress - Destination address as a string
* @param options - Optional travel mode and unit settings
* @returns Travel duration in minutes, or null if calculation fails
* @throws Error if API key is missing or API request fails critically
*/
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Docstring is misleading — HTTP errors do not propagate to callers.

The @throws annotation claims the function throws when "API request fails critically" (line 41), but the throw on line 72 is inside the try block and is caught by the catch on line 101, which returns null. Only the missing-API-key check (line 50–52) actually throws. Update the JSDoc to reflect reality.

📝 Suggested docstring fix
  * `@returns` Travel duration in minutes, or null if calculation fails
- * `@throws` Error if API key is missing or API request fails critically
+ * `@throws` Error if GOOGLE_MAPS_API_KEY is not configured
  */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Calculates travel time between two addresses using Google Maps Distance Matrix API
*
* @param originAddress - Origin address as a string
* @param destinationAddress - Destination address as a string
* @param options - Optional travel mode and unit settings
* @returns Travel duration in minutes, or null if calculation fails
* @throws Error if API key is missing or API request fails critically
*/
/**
* Calculates travel time between two addresses using Google Maps Distance Matrix API
*
* `@param` originAddress - Origin address as a string
* `@param` destinationAddress - Destination address as a string
* `@param` options - Optional travel mode and unit settings
* `@returns` Travel duration in minutes, or null if calculation fails
* `@throws` Error if GOOGLE_MAPS_API_KEY is not configured
*/
🤖 Prompt for AI Agents
In `@src/lib/google-maps.ts` around lines 34 - 42, The JSDoc for the Google Maps
travel-time function is inaccurate: only the missing-API-key check actually
throws, while HTTP/API errors are caught and the function returns null; update
the docblock for the function (the "Calculates travel time between two
addresses..." comment) to remove or narrow the `@throws` description to only the
missing API key case (mention the specific missing-key check) and clarify in
`@returns` that API request failures result in a null return rather than an
exception; reference the function name/description in the file so you update the
correct comment.

Copy link
Contributor

@burtonjong burtonjong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/app/debug/bookings/page.tsx (1)

365-408: Consider extracting the IIFE-rendered driver schedule table to a named component.

The immediately-invoked function expression inside JSX is functional but increases cognitive load. Extracting it to <DriverScheduleTable bookings={...} day={...} /> would improve readability and testability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/debug/bookings/page.tsx` around lines 365 - 408, The JSX contains an
IIFE that renders the driver schedule; extract it into a named component (e.g.,
DriverScheduleTable) to improve readability and testability: create a new React
component DriverScheduleTable that accepts props bookings (from
allBookingsQuery.data), day (selectedDay) and driverId (form.values.driverId),
encapsulate the filtering (using bookingOverlapsDay), sorting, and rendering
logic (including using formatTimeSlot and styles.bookedSlotsTableBody) inside
that component, and replace the IIFE in page.tsx with <DriverScheduleTable
bookings={allBookingsQuery.data ?? []} day={selectedDay}
driverId={form.values.driverId} />.
src/server/db/booking-schema.ts (1)

43-51: Consider a supplementary index to support validateTravelBetweenAdjacentBookings.

bookings_driver_overlap_idx on (driverId, status, startTime, endTime) fits findOverlappingBooking well (leading driverId equality + startTime range). However, the adjacent-booking queries in validateTravelBetweenAdjacentBookings filter on endTime ≤ x (prev-booking) and startTime ≥ x (next-booking) with a ne(status, "cancelled") predicate. Because ne on the second column (status) breaks the index prefix continuity, endTime (4th column) is effectively unreachable for the prev-booking lookup.

A supplementary partial index (or a separate index on (driverId, endTime)) would better serve the prev-booking query:

-- PostgreSQL partial index (Drizzle doesn't expose .where() on index yet; apply as a raw migration)
CREATE INDEX bookings_driver_end_time_idx
  ON bookings (driver_id, end_time)
  WHERE status <> 'cancelled';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/db/booking-schema.ts` around lines 43 - 51, The current composite
index bookings_driver_overlap_idx (driverId, status, startTime, endTime) doesn't
support the prev/next lookups used by validateTravelBetweenAdjacentBookings
because the ne(status, "cancelled") predicate breaks the index prefix; add a
supplementary index optimized for the prev-booking query—e.g., create a new
index named bookings_driver_end_time_idx on (driver_id, end_time) that is either
a partial index with WHERE status <> 'cancelled' or a standalone (driverId,
endTime) index; since Drizzle doesn't expose .where() on indexes yet, implement
the partial index as a raw SQL migration (or add the plain (driverId, endTime)
index) so validateTravelBetweenAdjacentBookings can efficiently find the
previous booking by endTime.
src/server/api/routers/bookings.ts (1)

288-292: External API call inside a tRPC query — cache/rate-limit in production.

getEstimatedEndTime calls getTravelTimeMinutes (Google Maps) on every execution. As a query endpoint it can be called by React Query on every mount/focus. The debug page sets staleTime: 0, which causes frequent refetches. Before exposing this to production UI, add a reasonable staleTime on the client and consider server-side result caching to reduce API quota consumption.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/api/routers/bookings.ts` around lines 288 - 292, The query
endpoint that computes drivingTimeMinutes calls getTravelTimeMinutes (via
getEstimatedEndTime) on every request which can exhaust Google Maps quota;
update the .query handler (where drivingTimeMinutes is computed) to: 1) add a
server-side cache keyed by the input pair (pickupAddress + destinationAddress)
with a sensible TTL (e.g., 5–15 minutes) using your app cache (in-memory or
Redis) and return the cached travel time when present; 2) fall back to
FALLBACK_TRAVEL_MINUTES only if both cache miss and API call fail; and 3) keep
the API call guarded with try/catch to avoid throwing on external API errors.
Also ensure the client that calls this tRPC query sets a non‑zero staleTime
(e.g., several minutes) in React Query to avoid refetch storms.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/debug/bookings/page.tsx`:
- Around line 53-65: The function bookingOverlapsDay mixes local-time day
boundaries with UTC booking timestamps; update bookingOverlapsDay to compute
dayStart/dayEnd in UTC (e.g., construct dayStart via
Date.UTC(day.getUTCFullYear(), day.getUTCMonth(), day.getUTCDate()) or call
setUTCHours(0,0,0,0) and dayEnd via setUTCHours(23,59,59,999)) so that the day
boundaries and the parsed booking times (new Date(booking.startTime)/endTime)
are in the same UTC reference frame before comparing start/end/getTime.

In `@src/server/api/routers/bookings.ts`:
- Around line 362-370: There is a TOCTOU race because validateDriverForSlot
(read) and the subsequent ctx.db.insert (write) in the create mutation — and
similarly the update mutation that calls validateDriverForSlot — are separate
operations; wrap the read+write in a single transaction with a stricter
isolation level (e.g., START TRANSACTION; SET TRANSACTION ISOLATION LEVEL
REPEATABLE READ) so validateDriverForSlot runs inside the same transaction
context before calling ctx.db.insert/ctx.db.update, then commit; alternatively
acquire a PostgreSQL advisory lock keyed by driverId or add an exclusion
constraint on the tsrange to prevent overlapping bookings at the DB level and
remove the race. Ensure the fixes are applied to the create handler that calls
validateDriverForSlot and the update handler that calls the same function
(validateDriverForSlot and the corresponding ctx.db.update).
- Around line 127-130: When getTravelTimeMinutes(prevBooking.destinationAddress,
pickupAddress) returns null you silently use FALLBACK_TRAVEL_MINUTES; change
this to log a warning whenever a fallback is used so API failures
(network/quota/invalid address) are visible: after calling getTravelTimeMinutes,
if the result is null, call the existing logger (e.g., logger or processLogger)
to warn that travel-time lookup failed, include the from/to addresses
(prevBooking.destinationAddress and pickupAddress) and that
FALLBACK_TRAVEL_MINUTES is being used, then proceed to compute requiredMinutes =
FALLBACK_TRAVEL_MINUTES + TRAVEL_BUFFER_MINUTES; apply the identical
logging/fallback pattern to the next-booking travel check as well.

---

Duplicate comments:
In `@src/server/api/routers/bookings.ts`:
- Around line 222-235: The duplicate review comment indicates no code change is
needed because listDrivers already conditionally includes email for admins via
the isAdmin check in the protectedProcedure query; resolve this by removing or
marking the duplicate review comment as resolved in the PR (no code edits to
ctx.db/select or the isAdmin conditional in listDrivers are necessary).
- Around line 515-524: Remove the leftover duplicate review marker and clarify
the intent around driver revalidation: delete the stray "[duplicate_comment]"
and, if needed, add a short inline comment near the driverId /
needsDriverValidation logic (variables: driverId, needsDriverValidation,
updatesToApply, existing) explaining that driver revalidation is intentionally
triggered when driverId is present and any of driver/time/address fields change;
keep the boolean condition as-is.

---

Nitpick comments:
In `@src/app/debug/bookings/page.tsx`:
- Around line 365-408: The JSX contains an IIFE that renders the driver
schedule; extract it into a named component (e.g., DriverScheduleTable) to
improve readability and testability: create a new React component
DriverScheduleTable that accepts props bookings (from allBookingsQuery.data),
day (selectedDay) and driverId (form.values.driverId), encapsulate the filtering
(using bookingOverlapsDay), sorting, and rendering logic (including using
formatTimeSlot and styles.bookedSlotsTableBody) inside that component, and
replace the IIFE in page.tsx with <DriverScheduleTable
bookings={allBookingsQuery.data ?? []} day={selectedDay}
driverId={form.values.driverId} />.

In `@src/server/api/routers/bookings.ts`:
- Around line 288-292: The query endpoint that computes drivingTimeMinutes calls
getTravelTimeMinutes (via getEstimatedEndTime) on every request which can
exhaust Google Maps quota; update the .query handler (where drivingTimeMinutes
is computed) to: 1) add a server-side cache keyed by the input pair
(pickupAddress + destinationAddress) with a sensible TTL (e.g., 5–15 minutes)
using your app cache (in-memory or Redis) and return the cached travel time when
present; 2) fall back to FALLBACK_TRAVEL_MINUTES only if both cache miss and API
call fail; and 3) keep the API call guarded with try/catch to avoid throwing on
external API errors. Also ensure the client that calls this tRPC query sets a
non‑zero staleTime (e.g., several minutes) in React Query to avoid refetch
storms.

In `@src/server/db/booking-schema.ts`:
- Around line 43-51: The current composite index bookings_driver_overlap_idx
(driverId, status, startTime, endTime) doesn't support the prev/next lookups
used by validateTravelBetweenAdjacentBookings because the ne(status,
"cancelled") predicate breaks the index prefix; add a supplementary index
optimized for the prev-booking query—e.g., create a new index named
bookings_driver_end_time_idx on (driver_id, end_time) that is either a partial
index with WHERE status <> 'cancelled' or a standalone (driverId, endTime)
index; since Drizzle doesn't expose .where() on indexes yet, implement the
partial index as a raw SQL migration (or add the plain (driverId, endTime)
index) so validateTravelBetweenAdjacentBookings can efficiently find the
previous booking by endTime.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/server/db/booking-schema.ts (1)

45-50: bookings_driver_overlap_idx covers the common overlap query pattern, but a secondary range predicate on endTime will be a residual filter.

A standard PostgreSQL B-tree index on (driverId, status, startTime, endTime) efficiently handles the equality filter on driverId/status and a range scan on startTime, but the condition endTime > :bookingStart cannot be range-scanned simultaneously — it remains a row-level filter after the index scan. For the expected booking volumes in this application this is acceptable, but be aware that query planning may scan more index pages than a true range index would.

The separately added bookings_driver_end_time_idx on (driverId, endTime) is a good complement and not redundant: since status sits between driverId and endTime in the overlap index, PostgreSQL cannot use the overlap index for queries that filter only on (driverId, endTime) without a status predicate.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/db/booking-schema.ts` around lines 45 - 50, The overlap index
bookings_driver_overlap_idx on (driverId, status, startTime, endTime) cannot
fully satisfy queries that need a separate range on endTime (endTime >
:bookingStart) because PostgreSQL can only range-scan one trailing column, so
endTime becomes a residual filter; keep the complementary
bookings_driver_end_time_idx on (driverId, endTime) and add a brief inline
comment next to the index definitions (referencing bookings_driver_overlap_idx
and bookings_driver_end_time_idx and
table.driverId/table.status/table.startTime/table.endTime) explaining this
limitation and why the second index is required, or if you prefer to optimize
different query shapes consider adding/ordering an index specifically for the
common query (e.g., (driverId, endTime, startTime)) after profiling.
src/app/debug/bookings/page.tsx (1)

240-255: Consider adding a staleTime to availabilityQuery to avoid hammering the Google Maps API on every keystroke.

Once canCheckAvailability is true, any edit to pickupAddress or destinationAddress changes the query key and fires a new request immediately (default staleTime: 0). Per the AI summary, isDriverAvailable may delegate to getTravelTimeMinutes in src/lib/google-maps.ts. A short cache window avoids redundant external calls while the user is still typing.

♻️ Suggested addition
   { enabled: canCheckAvailability },
+  { enabled: canCheckAvailability, staleTime: 30 * 1000 },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/debug/bookings/page.tsx` around lines 240 - 255, The query for driver
availability (availabilityQuery using api.bookings.isDriverAvailable.useQuery)
fires on every change because staleTime defaults to 0; add a small staleTime
(e.g., 1000–2000 ms) to the query options so edits to
pickupAddress/destinationAddress don't immediately refetch while the user is
typing, keeping canCheckAvailability logic unchanged and preserving caching for
calls that may delegate to getTravelTimeMinutes.
src/server/api/routers/bookings.ts (1)

29-29: travelTimeCache never evicts expired entries.

Expired entries are checked on read but never removed; in a long-running Node process they accumulate indefinitely. A simple prune-on-miss keeps memory bounded.

♻️ Minimal eviction on read
       const cached = travelTimeCache.get(cacheKey);
       let drivingTimeMinutes: number;
       if (cached && cached.expiresAt > now) {
         drivingTimeMinutes = cached.value;
       } else {
+        travelTimeCache.delete(cacheKey); // prune expired entry
         const apiResult = await getTravelTimeMinutes(input.pickupAddress, input.destinationAddress);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/api/routers/bookings.ts` at line 29, travelTimeCache is never
removing expired entries which causes unbounded memory growth; modify the access
logic that reads from travelTimeCache (the Map named travelTimeCache) to prune
expired entries when they are encountered (e.g., on a cache miss or when
checking an entry's expiresAt) by deleting Map keys whose expiresAt <=
Date.now(), and optionally perform a small bounded sweep (e.g., iterate a few
keys) on misses to remove other expired items so the Map stays bounded during
long-running processes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/server/api/routers/bookings.ts`:
- Around line 524-526: The update input schema for the bookings router is
missing the same endTime > startTime validation present in the create schema;
update the Zod schema used by the update resolver (the object containing
startTime: z.string().datetime().optional(), endTime:
z.string().datetime().optional()) to add a .refine() that enforces when both
startTime and endTime are provided that endTime > startTime (same logic as in
the create schema), so that findOverlappingBooking and row integrity are
preserved.
- Around line 319-325: The current logic stores FALLBACK_TRAVEL_MINUTES into
travelTimeCache when getTravelTimeMinutes returns null, which causes silent
serving of the fallback for TRAVEL_TIME_CACHE_TTL_MS; change the behavior in the
block handling getTravelTimeMinutes so that if apiResult is null you set
drivingTimeMinutes = FALLBACK_TRAVEL_MINUTES but do NOT write to travelTimeCache
(skip the cache write) and log a warning/notice similar to
validateTravelBetweenAdjacentBookings to surface the failure; keep cache writes
only when apiResult is a non-null number and continue to use the same cache key
and TTL constants (travelTimeCache, FALLBACK_TRAVEL_MINUTES,
TRAVEL_TIME_CACHE_TTL_MS, getTravelTimeMinutes,
validateTravelBetweenAdjacentBookings).
- Around line 35-47: assertCanAccessBooking currently only allows admins or
agency owners, which blocks assigned drivers; change its signature to accept an
optional bookingDriverId (e.g., assertCanAccessBooking(session, agencyId,
bookingDriverId?)) and treat the caller as allowed if role === "admin" ||
agencyId === session.user.id || bookingDriverId === session.user.id. Then update
getById to fetch the booking first and call assertCanAccessBooking(session,
booking.agencyId, booking.driverId) before returning; leave update and cancel
calls using the original two-argument semantics if drivers should remain unable
to mutate bookings.

---

Duplicate comments:
In `@src/app/debug/bookings/page.tsx`:
- Around line 53-64: The function bookingOverlapsDay builds UTC-day boundaries
using UTC getters from the provided local-midnight Date, which misaligns for
UTC+ zones; update bookingOverlapsDay to use local getters (getFullYear,
getMonth, getDate) when computing dStart/dEnd so the UTC boundaries match how
selectedDay/computedStart are constructed and keep the overlap check (start <
dEnd && end > dStart) unchanged; look for bookingOverlapsDay and ensure it
mirrors the same "local getters -> UTC boundary" approach as computedStart to
fix the off-by-one in UTC+ timezones.

---

Nitpick comments:
In `@src/app/debug/bookings/page.tsx`:
- Around line 240-255: The query for driver availability (availabilityQuery
using api.bookings.isDriverAvailable.useQuery) fires on every change because
staleTime defaults to 0; add a small staleTime (e.g., 1000–2000 ms) to the query
options so edits to pickupAddress/destinationAddress don't immediately refetch
while the user is typing, keeping canCheckAvailability logic unchanged and
preserving caching for calls that may delegate to getTravelTimeMinutes.

In `@src/server/api/routers/bookings.ts`:
- Line 29: travelTimeCache is never removing expired entries which causes
unbounded memory growth; modify the access logic that reads from travelTimeCache
(the Map named travelTimeCache) to prune expired entries when they are
encountered (e.g., on a cache miss or when checking an entry's expiresAt) by
deleting Map keys whose expiresAt <= Date.now(), and optionally perform a small
bounded sweep (e.g., iterate a few keys) on misses to remove other expired items
so the Map stays bounded during long-running processes.

In `@src/server/db/booking-schema.ts`:
- Around line 45-50: The overlap index bookings_driver_overlap_idx on (driverId,
status, startTime, endTime) cannot fully satisfy queries that need a separate
range on endTime (endTime > :bookingStart) because PostgreSQL can only
range-scan one trailing column, so endTime becomes a residual filter; keep the
complementary bookings_driver_end_time_idx on (driverId, endTime) and add a
brief inline comment next to the index definitions (referencing
bookings_driver_overlap_idx and bookings_driver_end_time_idx and
table.driverId/table.status/table.startTime/table.endTime) explaining this
limitation and why the second index is required, or if you prefer to optimize
different query shapes consider adding/ordering an index specifically for the
common query (e.g., (driverId, endTime, startTime)) after profiling.

Comment on lines +35 to +47
function assertCanAccessBooking(
session: { user: { id: string; role?: string | null } },
agencyId: string,
): void {
const role = session.user.role ?? "user";
const allowed = role === "admin" || agencyId === session.user.id;
if (!allowed) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You don't have permission to access this booking.",
});
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Drivers are blocked from getById for bookings they are assigned to.

assertCanAccessBooking only passes when the caller is an admin or when agencyId === session.user.id (agency-role users). However, getAll explicitly includes eq(bookings.driverId, userId) in its filter, so assigned drivers can list a booking through getAll but will receive FORBIDDEN when calling getById, update, or cancel on that same booking ID.

For getById at minimum, the check should also allow the assigned driver:

🐛 Proposed fix
 function assertCanAccessBooking(
   session: { user: { id: string; role?: string | null } },
   agencyId: string,
+  driverId?: string | null,
 ): void {
   const role = session.user.role ?? "user";
-  const allowed = role === "admin" || agencyId === session.user.id;
+  const allowed =
+    role === "admin" ||
+    agencyId === session.user.id ||
+    (!!driverId && driverId === session.user.id);
   if (!allowed) {
     throw new TRPCError({
       code: "FORBIDDEN",
       message: "You don't have permission to access this booking.",
     });
   }
 }

Then update the getById call:

-    assertCanAccessBooking(ctx.session, row.agencyId);
+    assertCanAccessBooking(ctx.session, row.agencyId, row.driverId);

Keep update and cancel using the stricter two-argument form if drivers should not be able to mutate bookings.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function assertCanAccessBooking(
session: { user: { id: string; role?: string | null } },
agencyId: string,
): void {
const role = session.user.role ?? "user";
const allowed = role === "admin" || agencyId === session.user.id;
if (!allowed) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You don't have permission to access this booking.",
});
}
}
function assertCanAccessBooking(
session: { user: { id: string; role?: string | null } },
agencyId: string,
driverId?: string | null,
): void {
const role = session.user.role ?? "user";
const allowed =
role === "admin" ||
agencyId === session.user.id ||
(!!driverId && driverId === session.user.id);
if (!allowed) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You don't have permission to access this booking.",
});
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/api/routers/bookings.ts` around lines 35 - 47,
assertCanAccessBooking currently only allows admins or agency owners, which
blocks assigned drivers; change its signature to accept an optional
bookingDriverId (e.g., assertCanAccessBooking(session, agencyId,
bookingDriverId?)) and treat the caller as allowed if role === "admin" ||
agencyId === session.user.id || bookingDriverId === session.user.id. Then update
getById to fetch the booking first and call assertCanAccessBooking(session,
booking.agencyId, booking.driverId) before returning; leave update and cancel
calls using the original two-argument semantics if drivers should remain unable
to mutate bookings.

Comment on lines +319 to +325
const apiResult = await getTravelTimeMinutes(input.pickupAddress, input.destinationAddress);
drivingTimeMinutes = apiResult ?? FALLBACK_TRAVEL_MINUTES;
travelTimeCache.set(cacheKey, {
value: drivingTimeMinutes,
expiresAt: now + TRAVEL_TIME_CACHE_TTL_MS,
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

API failure fallback is cached, suppressing retries for 10 minutes.

When getTravelTimeMinutes returns null (quota, network, invalid address), drivingTimeMinutes is set to FALLBACK_TRAVEL_MINUTES and that value is written into the cache. Every subsequent call for the same address pair within the TTL window will silently serve the stale fallback without retrying the real API. No warning is logged here either, unlike the equivalent guard in validateTravelBetweenAdjacentBookings.

🐛 Proposed fix — skip cache write on failure
-      const apiResult = await getTravelTimeMinutes(input.pickupAddress, input.destinationAddress);
-      drivingTimeMinutes = apiResult ?? FALLBACK_TRAVEL_MINUTES;
-      travelTimeCache.set(cacheKey, {
-        value: drivingTimeMinutes,
-        expiresAt: now + TRAVEL_TIME_CACHE_TTL_MS,
-      });
+      const apiResult = await getTravelTimeMinutes(input.pickupAddress, input.destinationAddress);
+      if (apiResult === null) {
+        console.warn("[getEstimatedEndTime] Travel time lookup failed; using fallback", {
+          from: input.pickupAddress,
+          to: input.destinationAddress,
+          fallback: FALLBACK_TRAVEL_MINUTES,
+        });
+      }
+      drivingTimeMinutes = apiResult ?? FALLBACK_TRAVEL_MINUTES;
+      if (apiResult !== null) {
+        travelTimeCache.set(cacheKey, {
+          value: drivingTimeMinutes,
+          expiresAt: now + TRAVEL_TIME_CACHE_TTL_MS,
+        });
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/api/routers/bookings.ts` around lines 319 - 325, The current logic
stores FALLBACK_TRAVEL_MINUTES into travelTimeCache when getTravelTimeMinutes
returns null, which causes silent serving of the fallback for
TRAVEL_TIME_CACHE_TTL_MS; change the behavior in the block handling
getTravelTimeMinutes so that if apiResult is null you set drivingTimeMinutes =
FALLBACK_TRAVEL_MINUTES but do NOT write to travelTimeCache (skip the cache
write) and log a warning/notice similar to validateTravelBetweenAdjacentBookings
to surface the failure; keep cache writes only when apiResult is a non-null
number and continue to use the same cache key and TTL constants
(travelTimeCache, FALLBACK_TRAVEL_MINUTES, TRAVEL_TIME_CACHE_TTL_MS,
getTravelTimeMinutes, validateTravelBetweenAdjacentBookings).

Comment on lines +524 to 526
startTime: z.string().datetime().optional(),
endTime: z.string().datetime().optional(),
}),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

update input lacks endTime > startTime validation, unlike create.

create enforces endTime > startTime via .refine(). update doesn't, so a client can pass an inverted range and corrupt the row. An inverted [startTime, endTime] also makes the overlap query in findOverlappingBooking behave unexpectedly (any booking can satisfy lt(startTime, endTime[past]) AND gt(endTime, startTime[future])).

🐛 Proposed fix
     z.object({
       id: z.number(),
       title: z.string().optional(),
       pickupAddress: z.string().optional(),
       destinationAddress: z.string().optional(),
       purpose: z.string().optional(),
       passengerInfo: z.string().optional(),
       driverId: z.string().optional().nullable(),
       status: StatusZ.optional(),
       startTime: z.string().datetime().optional(),
       endTime: z.string().datetime().optional(),
-    }),
+    }).refine(
+      (data) =>
+        data.startTime === undefined ||
+        data.endTime === undefined ||
+        new Date(data.endTime) > new Date(data.startTime),
+      { message: "endTime must be after startTime", path: ["endTime"] },
+    ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
startTime: z.string().datetime().optional(),
endTime: z.string().datetime().optional(),
}),
z.object({
id: z.number(),
title: z.string().optional(),
pickupAddress: z.string().optional(),
destinationAddress: z.string().optional(),
purpose: z.string().optional(),
passengerInfo: z.string().optional(),
driverId: z.string().optional().nullable(),
status: StatusZ.optional(),
startTime: z.string().datetime().optional(),
endTime: z.string().datetime().optional(),
}).refine(
(data) =>
data.startTime === undefined ||
data.endTime === undefined ||
new Date(data.endTime) > new Date(data.startTime),
{ message: "endTime must be after startTime", path: ["endTime"] },
),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/api/routers/bookings.ts` around lines 524 - 526, The update input
schema for the bookings router is missing the same endTime > startTime
validation present in the create schema; update the Zod schema used by the
update resolver (the object containing startTime:
z.string().datetime().optional(), endTime: z.string().datetime().optional()) to
add a .refine() that enforces when both startTime and endTime are provided that
endTime > startTime (same logic as in the create schema), so that
findOverlappingBooking and row integrity are preserved.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/app/debug/bookings/page.tsx (1)

447-451: Hardcoded "15 min" in the debug output won't track changes to PICKUP_WAIT_TIME_MINUTES.

The PICKUP_WAIT_TIME_MINUTES constant is already available via the import at the top of bookings.ts (server-side). The client already receives totalBookingMinutes and drivingTimeMinutes in the query result; the pickup-wait portion can be derived rather than hardcoded.

♻️ Derive pickup wait from API data
-              Total booking time: {estimatedEndQuery.data.totalBookingMinutes} min (15 min pickup
-              wait + {estimatedEndQuery.data.drivingTimeMinutes} min travel)
+              Total booking time: {estimatedEndQuery.data.totalBookingMinutes} min (
+              {estimatedEndQuery.data.totalBookingMinutes - estimatedEndQuery.data.drivingTimeMinutes} min pickup
+              wait + {estimatedEndQuery.data.drivingTimeMinutes} min travel)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/debug/bookings/page.tsx` around lines 447 - 451, Replace the
hardcoded "15 min" in the debug string with a derived pickup-wait value
calculated from the query result: compute pickupWait =
estimatedEndQuery.data.totalBookingMinutes -
estimatedEndQuery.data.drivingTimeMinutes (guard for undefined/null), then
render that pickupWait value (e.g., `${pickupWait} min`) instead of the literal
"15 min"; update the debug lines that reference
estimatedEndQuery.data.totalBookingMinutes and
estimatedEndQuery.data.drivingTimeMinutes to use this derived pickupWait so the
displayed pickup wait always matches the API data.
src/server/api/routers/bookings.ts (2)

409-412: tx as unknown as typeof db silences the type mismatch between db and its transaction object.

Drizzle doesn't provide a type for transactions, so you need to extract it yourself. Widening DbContext to accept both avoids the double cast at both call sites (lines 412 and 588):

♻️ Type-safe DbContext
-type DbContext = { db: typeof db };
+type DbTransaction = Parameters<Parameters<typeof db.transaction>[0]>[0];
+type DbContext = { db: typeof db | DbTransaction };

Then remove the casts at each call site:

-await validateDriverForSlot(
-  { db: tx as unknown as typeof db },
+await validateDriverForSlot(
+  { db: tx },
   { ... },
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/api/routers/bookings.ts` around lines 409 - 412, The current code
silences a type mismatch by casting the transaction object (tx) with "tx as
unknown as typeof db" when calling validateDriverForSlot; instead, update your
DbContext type so it accepts both the normal db and a transaction instance
(widen the context type or make it generic over the DB/transaction shape), then
change validateDriverForSlot (and any other functions that accept DbContext) to
use that widened/generic DbContext type so you can pass tx directly from
ctx.db.transaction without any casts; locate usages around
validateDriverForSlot, ctx.db.transaction, and other callers (the other cast
usage referenced in the review) and remove the double-casts once the DbContext
type is adjusted.

29-29: travelTimeCache never evicts expired entries — stale entries accumulate indefinitely.

The Map TTL is only checked on reads; expired entries are never removed. In a long-running server process, every unique address-pair queried will remain in memory forever. Consider either:

  • Adding a periodic cleanup (e.g. setInterval) to purge entries where expiresAt < Date.now(), or
  • Replacing the hand-rolled cache with an LRU eviction policy (e.g. lru-cache).

As a secondary note: the cache is process-local, so it provides no benefit on serverless deployments where each cold-start is a fresh module.

♻️ Minimal eviction example
 const travelTimeCache = new Map<string, { value: number; expiresAt: number }>();
+
+// Periodic cleanup of expired entries to prevent unbounded memory growth
+if (typeof setInterval !== "undefined") {
+  setInterval(() => {
+    const now = Date.now();
+    for (const [key, entry] of travelTimeCache) {
+      if (entry.expiresAt <= now) travelTimeCache.delete(key);
+    }
+  }, TRAVEL_TIME_CACHE_TTL_MS);
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/api/routers/bookings.ts` at line 29, The travelTimeCache Map
stores entries with expiresAt but never evicts them, causing unbounded memory
growth; modify the module to regularly purge expired entries by adding a
periodic cleanup (e.g., start a setInterval when the module initializes that
iterates travelTimeCache and deletes entries where entry.expiresAt < Date.now())
or swap the Map for an LRU cache implementation (e.g., replace travelTimeCache
with an instance from the lru-cache package configured with a max size and TTL)
and update any get/set usages in the functions that reference travelTimeCache to
use the new cache API; ensure the cleanup interval is cleared on shutdown if
using setInterval.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/server/api/routers/bookings.ts`:
- Around line 41-53: assertCanAccessBooking currently only allows admins and
agency owners (role "admin" or agencyId === session.user.id) which blocks
assigned drivers from accessing their own bookings; update the guard and its
call sites (getById, update, cancel) so it also permits a session user whose id
matches the booking.driverId. Concretely, change assertCanAccessBooking to
accept an optional driverId (e.g., assertCanAccessBooking(session, agencyId,
driverId?)) or accept the booking object, then in the body allow access when
session.user.id === driverId; update the calls in getById, update, and cancel to
pass the booking's driverId (or booking) before throwing TRPCError.
- Around line 328-336: The code currently caches FALLBACK_TRAVEL_MINUTES when
getTravelTimeMinutes returns null; change the logic so that when apiResult ===
null you set usedFallback = true and drivingTimeMinutes =
FALLBACK_TRAVEL_MINUTES but do NOT write to travelTimeCache (only write cache
entries when apiResult !== null). Locate the block around getTravelTimeMinutes,
cacheKey, travelTimeCache.set and adjust it so travelTimeCache.set is executed
only if apiResult is a real number (apiResult !== null), leaving
usedCached/usedFallback behavior unchanged.
- Around line 539-541: The update input schema for bookings currently defines
startTime and endTime as z.string().datetime().optional() but lacks the same
.refine() check used in the create schema to enforce endTime > startTime, so
supply of both can produce an inverted range and break findOverlappingBooking;
update the "update" input schema (the update Zod object that contains startTime
and endTime) to add a conditional refine that, when both startTime and endTime
are present, parses them and ensures endTime > startTime (mirroring the create
schema's validation) and returns a proper Zod error if the condition fails.

---

Nitpick comments:
In `@src/app/debug/bookings/page.tsx`:
- Around line 447-451: Replace the hardcoded "15 min" in the debug string with a
derived pickup-wait value calculated from the query result: compute pickupWait =
estimatedEndQuery.data.totalBookingMinutes -
estimatedEndQuery.data.drivingTimeMinutes (guard for undefined/null), then
render that pickupWait value (e.g., `${pickupWait} min`) instead of the literal
"15 min"; update the debug lines that reference
estimatedEndQuery.data.totalBookingMinutes and
estimatedEndQuery.data.drivingTimeMinutes to use this derived pickupWait so the
displayed pickup wait always matches the API data.

In `@src/server/api/routers/bookings.ts`:
- Around line 409-412: The current code silences a type mismatch by casting the
transaction object (tx) with "tx as unknown as typeof db" when calling
validateDriverForSlot; instead, update your DbContext type so it accepts both
the normal db and a transaction instance (widen the context type or make it
generic over the DB/transaction shape), then change validateDriverForSlot (and
any other functions that accept DbContext) to use that widened/generic DbContext
type so you can pass tx directly from ctx.db.transaction without any casts;
locate usages around validateDriverForSlot, ctx.db.transaction, and other
callers (the other cast usage referenced in the review) and remove the
double-casts once the DbContext type is adjusted.
- Line 29: The travelTimeCache Map stores entries with expiresAt but never
evicts them, causing unbounded memory growth; modify the module to regularly
purge expired entries by adding a periodic cleanup (e.g., start a setInterval
when the module initializes that iterates travelTimeCache and deletes entries
where entry.expiresAt < Date.now()) or swap the Map for an LRU cache
implementation (e.g., replace travelTimeCache with an instance from the
lru-cache package configured with a max size and TTL) and update any get/set
usages in the functions that reference travelTimeCache to use the new cache API;
ensure the cleanup interval is cleared on shutdown if using setInterval.

@promatty
Copy link
Contributor

promatty commented Feb 22, 2026

merge conflicts @Lujarios

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants